//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_SEMAPHORE_H
#	define NJ_SEMAPHORE_H

#	include "NjNinjaSyncLib.h"
#	include "NjLocker.h"
#	include "NjEventsSystem.h"
#	include "NjSemaphore_config.h"
#	include "NjConfiguration.h"

	/// \brief Implements a semaphore synchronization object
	///
	/// Semaphores include a counter. Lock operations decrement the counter, while Unlock operations increment it.
	/// If the semaphore reaches zero further Lock operations will block the calling thread. 
	/// Differently from Mutexes Lock and Unlock operations can be performed even by different threads.
	/// During construction you can set a maximum value to the counter.
	/// If Unlock is called too many times and the counter exceed the maximum value, in some build configurations an assertion will be triggered.
	class NjSemaphore
	{
	private:
		/// \brief Used to store the state of the kernel event object
		///
		/// This bit can be set only if the kernel object is allocated.
		/// When this bit is set it is expected that NjEventsSystem::TryWaitEvent operations will block the calling thread
		static const NjUint STATE_EVENT_UNSET     = 0x00000001;

		/// \brief Counter step used by the semaphore implementation
		static const NjUint STATE_COUNTER_ONE     = 0x00000002;

		/// \var static const NjUint STATE_COUNTER_MASK
		/// \brief Counter mask used by the semaphore implementation

#if NJ_PLATFORM_64_BIT
		static const NjUint STATE_COUNTER_MASK    = 0x00000001FFFFFFFEULL;
#else // NJ_PLATFORM_64_BIT
		static const NjUint STATE_COUNTER_MASK    = 0x0007FFFE;
#endif // NJ_PLATFORM_64_BIT

		/// \brief A tag counter used to avoid the ABA problem
		/// \sa http://en.wikipedia.org/wiki/ABA_problem
		static const NjUint STATE_ABA_COUNTER_ONE = STATE_COUNTER_MASK+STATE_COUNTER_ONE;

	public:
		/// \brief the maximum value that can be passed to NjSemaphore constructor, \em uMaxCounter parameter
		static const NjUint MAX_MAX_COUNTER       = STATE_COUNTER_MASK/STATE_COUNTER_ONE;

		/// \brief Defines a scoped-lock for NjSemaphore
		typedef NjLocker<NjSemaphore> Locker;

		/// \brief Defines the timeout type
		typedef NjEventsSystem::TimeMs TimeMs;

		/// \brief during construction you can set the initial counter value `uCounter` and the maximum counter value `uMaxCounter`. The following conditions should be respected `uCounter>=0 && uCounter<=uMaxCounter` and `uMaxCounter>0 && uMaxCounter<=NjSemaphore::MAX_MAX_COUNTER`
		NjSemaphore(NjUint uCounter=0, NjUint uMaxCounter = MAX_MAX_COUNTER);

		/// \brief Destroys this NjSemaphore instance
		~NjSemaphore();

		/// \brief Decrements the semaphore counter or, if it already reached zero, puts the calling thread into suspended state
		///
		/// In this second case the thread is resumed once Unlock is called by another thread
		void Lock();

		/// \brief Attempts to decrement the semaphore counter or, if it already reached zero, puts the calling thread into suspended state
		///
		/// In this second case the thread is resumed once Unlock is called by another thread or once the time out \em uTimeMs expires.
		/// TryLock(0) will attempt to decrement the semaphore counter and will return immediately if it already reached zero, without waiting further.
		/// TryLock(NjEventsSystem::WAIT_FOREVER) is identical to Lock
		/// \param uTimeMs the time out in milliseconds
		/// \return If the semaphore is decremented within the provided time out, TryLock will return NJ_TRUE otherwise it will return NJ_FALSE
		NjBool TryLock(TimeMs uTimeMs);

		/// \brief Increments the semaphore counter of \em uCounter
		///
		/// If the counter is zero and other threads are waiting on it, the waiting threads are resumed. 
		/// Each thread will decrement again the counter, so in this at maximum \em uCounter can be resumed.
		/// In some build configurations is checked if the counter execeed the maximum value defined during construction (constructor parameter \em uMaxCounter).
		/// If the counter exceeds this value an assertion is triggered
		/// \param uCounter value used to increment the semaphore
		void Unlock(NjUint uCounter=1);

	private:
		/// \brief The kernel event type
		typedef NjEventsSystem::Event Event;

		/// \brief The current state of the semaphore
		NjAtomic<NjUint> m_kState;

		/// \brief The kernel event assigned to this semaphore
		NjAtomic<Event *> m_kEvent;

#	if NJ_SEMAPHORE_ENFORCE_MAX_COUNTER
		/// \brief The max counter value
		NjUint m_uMaxCounter;
#	endif // NJ_SEMAPHORE_ENFORCE_MAX_COUNTER

#	if NJ_SEMAPHORE_TRACKING_ENABLED
		/// \brief Class to track semaphores usage
		class TrackerEntry;

		/// \copydoc TrackerEntry
		class Tracker;
#	endif // NJ_SEMAPHORE_TRACKING_ENABLED

#	if NJ_SEMAPHORE_STORE_USERS_ENABLED
		/// \brief Called immediately entering into a method
		void UserEnter();

		/// \brief Called before leaving a method
		void UserLeave();

		/// \brief This counter stores the current threads accessing to this resource
		NjAtomic<NjUint> m_kUsers;
#	endif // NJ_SEMAPHORE_STORE_USERS_ENABLED

		NJ_DISABLE_COPY(NjSemaphore);
	};

#	include "NjSemaphore_inline.h"

#endif // NJ_SEMAPHORE_H
